﻿using Dubbo.Common;
using Dubbo.Common.Extension;
using Dubbo.Common.Utils;
using Dubbo.Rpc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace Dubbo.Config.Api
{
    public class ServiceConfig<T> : AbstractServiceConfig
    {
        //private static readonly IProtocol protocol; // = ExtensionLoader.getExtensionLoader(IProtocol.class).getAdaptiveExtension();

        //private static readonly IProxyFactory proxyFactory; // = ExtensionLoader.getExtensionLoader(IProxyFactory.class).getAdaptiveExtension();

        private IProtocol protocol;
        private IProxyFactory proxyFactory;

        private static readonly Dictionary<string, int> RANDOM_PORT_MAP = new Dictionary<string, int>();

        // 接口类型
        private string interfaceName;
        public string Interface
        {
            get
            {
                return interfaceName;
            }
            set
            {
                this.interfaceName = value;
                if (string.IsNullOrEmpty(Id))
                {
                    Id = interfaceName;
                }
            }
        }

        // 接口实现类引用
        public T Ref { get; set; }

        // 服务名称
        private string path;
        public string Path
        {
            get
            { return path; }
            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    throw new ArgumentNullException("Path");
                }
                path = value;
            }
        }

        // 方法配置
        //private List<MethodConfig> methods;

        public ProviderConfig Provider { get; set; }

        private readonly List<URL> urls = new List<URL>();

        private readonly List<IExporter> exporters = new List<IExporter>();

        private volatile bool exported;
        public bool IsExported
        {
            get { return exported; }
        }
        
        private volatile bool unexported;
        public bool IsUnexported
        {
            get { return unexported; }
        }

        private volatile string generic;
        
        public ServiceConfig()
        {
        }

        //public ServiceConfig(Service service)
        //{
        //    appendAnnotation(Service.class, service);
        //}

        public URL ToUrl()
        {
            return urls == null || urls.Count == 0 ? null : urls[0];
        }

        public List<URL> ToUrls()
        {
            return urls;
        }
        
        public void Export()
        {
            //if (provider != null)
            //{
            //    if (export == null)
            //    {
            //        export = provider.getExport();
            //    }
            //    if (delay == null)
            //    {
            //        delay = provider.getDelay();
            //    }
            //}
            if (IsExport != null && !IsExport.Value)
            {
                return;
            }
            if (Delay != null && Delay.Value > 0)
            {
                Task.Run(async () =>
                {
                    await Task.Delay(Delay.Value);
                    DoExport();
                });
            }
            else
            {
                DoExport();
            }
        }

        protected void DoExport()
        {
            lock (obj)
            {
                if (unexported)
                {
                    throw new InvalidOperationException("Already unexported!");
                }
                if (exported)
                {
                    return;
                }
                exported = true;

                CheckApplication();
                //checkRegistry();
                CheckProtocol();
                //appendProperties(this);
                //checkStubAndMock(interfaceClass);
                if (string.IsNullOrEmpty(path))
                {
                    path = interfaceName;
                }
                DoExportUrls();
            }
        }

        private static int GetRandomPort(string protocol)
        {
            protocol = protocol.ToLower();
            if (RANDOM_PORT_MAP.ContainsKey(protocol))
            {
                return RANDOM_PORT_MAP[protocol];
            }
            return int.MinValue;
        }

        private static void PutRandomPort(string protocol, int port)
        {
            protocol = protocol.ToLower();
            if (!RANDOM_PORT_MAP.ContainsKey(protocol))
            {
                RANDOM_PORT_MAP[protocol] = port;
            }
        }

        private void DoExportUrls()
        {
            List<URL> registryURLs = LoadRegistries(true);
            foreach (ProtocolConfig protocolConfig in Protocols)
            {
                DoExportUrlsFor1Protocol(protocolConfig, registryURLs);
            }
        }

        private void DoExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs)
        {
            string name = protocolConfig.Name;
            if (string.IsNullOrEmpty(name))
            {
                name = "thrift";
            }

            proxyFactory = ExtensionLoader.GetExtension<IProxyFactory>("transparent");
            protocol = ExtensionLoader.GetExtension<IProtocol>(name);

            string host = protocolConfig.Host;
            if (Provider != null && (string.IsNullOrEmpty(host)))
            {
                host = Provider.Host;
            }
            bool anyhost = false;

            if (NetUtils.IsInvalidLocalHost(host))
            {
                anyhost = true;
                host = NetUtils.GetLocalHost();
            }

            int? port = protocolConfig.Port;
            if (Provider != null && (port == null || port.Value == 0))
            {
                port = Provider.Port;
            }
            int defaultPort = protocol.GetDefaultPort();
            if (port == null || port == 0)
            {
                port = defaultPort;
            }
            if (port == null || port <= 0)
            {
                port = GetRandomPort(name);
                if (port == null || port < 0)
                {
                    port = NetUtils.GetAvailablePort(defaultPort);
                    PutRandomPort(name, port.Value);
                }
                //logger.warn("Use random available port(" + port + ") for protocol " + name);
            }

            Dictionary<string, string> map = new Dictionary<string, string>();
            if (anyhost)
            {
                map[Constants.ANYHOST_KEY] = "true";
            }
           
            // 导出服务
            //string contextPath = protocolConfig.getContextpath();
            //if ((contextPath == null || contextPath.length() == 0) && provider != null)
            //{
            //    contextPath = provider.getContextpath();
            //}
            //URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
            URL url = new URL(name, host, port.Value, path, map);

            //if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
            //        .hasExtension(url.getProtocol())) {
            //    url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
            //            .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
            //}

            string scope = url.GetParameter(Constants.SCOPE_KEY);
            //配置为none不暴露
            if (!Constants.SCOPE_NONE.Equals(scope, StringComparison.OrdinalIgnoreCase))
            {
                //配置不是remote的情况下做本地暴露 (配置为remote，则表示只暴露远程服务)
                if (!Constants.SCOPE_REMOTE.Equals(scope, StringComparison.OrdinalIgnoreCase))
                {
                    //exportLocal(url);
                }
                //如果配置不是local则暴露为远程服务.(配置为local，则表示只暴露远程服务)
                if (!Constants.SCOPE_LOCAL.Equals(scope, StringComparison.OrdinalIgnoreCase))
                {
                    //if (logger.isInfoEnabled())
                    //{
                    //    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                    //}

                    if (registryURLs != null && registryURLs.Count != 0)
                    {
                        foreach (var registryURL in registryURLs)
                        {
                            //url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
                            //URL monitorUrl = loadMonitor(registryURL);
                            URL monitorUrl = new URL(name, host, port.Value);
                            if (monitorUrl != null)
                            {
                                //url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                            }
                            IInvoker<T> invoker = proxyFactory.GetInvoker(Ref, registryURL);
                            IExporter<T> exporter = protocol.Export(invoker);
                            exporters.Add(exporter);
                        }
                    }
                    else
                    {
                        IInvoker<T> invoker = proxyFactory.GetInvoker(Ref, url);
                        IExporter<T> exporter = protocol.Export(invoker);
                        exporters.Add(exporter);
                    }
                }
            }
            this.urls.Add(url);
        }
        
        private void CheckProtocol()
        {
            if ((Protocols == null || Protocols.Count == 0) && Provider != null)
            {
                Protocols = Provider.Protocols;
            }
            if (Protocols == null || Protocols.Count == 0)
            {
                Protocols = new List<ProtocolConfig> { new ProtocolConfig() };
            }
            foreach (ProtocolConfig protocolConfig in Protocols)
            {
                if (string.IsNullOrEmpty(protocolConfig.Name))
                {
                    protocolConfig.Name = "thrift";
                }
                //appendProperties(protocolConfig);
            }
        }
        
        private object obj = new object();

        public void Unexport()
        {
            lock (obj)
            {
                if (!exported)
                {
                    return;
                }
                if (unexported)
                {
                    return;
                }
                if (exporters != null && exporters.Count > 0)
                {
                    foreach (IExporter exporter in exporters)
                    {
                        try
                        {
                            exporter.Unexport();
                        }
                        catch (Exception ex)
                        {
                            //logger.warn("unexpected err when unexport" + exporter, t);
                        }
                    }
                    exporters.Clear();
                }
                unexported = true;
            }
        }
    }
}
